﻿using Newtonsoft.Json;
using Sustainalytics.Auth.Common;
using Sustainalytics.DataLayer;
using Sustainalytics.EmailHelper;
using Sustainalytics.Entities;
using Sustainalytics.Replication.Publisher;
using Sustainalytics.Utils;
using Sustainalytics.Utils.Logging.Serilog;
using Sustainalytics.Utils.Logging.Serilog.Messages;
using Sustainalytics.Utils.Serilog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Sustainalytics.AuthOperations
{
    public class AuthOperations : IAuthOperations
    {
        public const string DEFAULT_IP_LOGIN_PASSWORD = "93783f0f-5570-4896-8a5a-56f7958262ff";

        private IAccountsRepository _accountsRepository;
        private IEmailHelper _emailHelper;
        private static string _passwordChangeOnlyEmailTemplate;
        private static string _passwordRecoveryInactiveAccountEmailBodyFile;
        private static string _passwordRecoveryInactiveAccountEmailSubject = "Change your password for Sustainalytics’ Global Access Portal";
        private static string _passwordChangeEmailBodyFile;
        private static string _accesGrantedEmailBodyFile;
        private readonly static string _passwordChangeEmailSubject = "Change your password for Sustainalytics' Global Access Portal";
        private static string _passwordSuccessfullyChangedEmailBodyFile;
        private readonly static string _passwordSuccessfullyChangedEmailSubject = "Sustainalytics password change confirmation";
        private readonly static string _accessGrantedEmailSubject = "Welcome to Sustainalytics’ Global Access Portal";
        private readonly static string _accessGrantedEmailBody = "You have been granted access to Sustainalytics’ Global Access. The temporary key provided is valid for 7 days.";
        private readonly static string _passwordExpiredEmailBody = "Your password for Sustainalytics’ Global Access has expired and needs to be reset. The temporary key provided is valid for 7 days.";
        private readonly static string _passwordResetEmailBody = "Your password for Sustainalytics’ Global Access has been reset, you are now requested to change your password. The temporary key provided is valid for 7 days.";
        private string _clientPlatformUrl;
        private ServiceBusPublisher _serviceBusPublisher;

       public AuthOperations() { }

        public AuthOperations (IAccountsRepository accountsRepository, string clientPlatformUrl, Dictionary<string, string> serviceBusParams)
        {
            _accountsRepository = accountsRepository;
            _emailHelper = EmailHelper.EmailHelper.GetInstance();
            _clientPlatformUrl = clientPlatformUrl;
            _accesGrantedEmailBodyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates\\ActivateAccount.htm");
            _passwordChangeOnlyEmailTemplate = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates\\PasswordChangedEmailOnly.htm");
            _passwordChangeEmailBodyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates\\PasswordChangeEmailBody.htm");
            _passwordSuccessfullyChangedEmailBodyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates\\PasswordSuccessfullyChangedEmailBody.htm");
            _passwordRecoveryInactiveAccountEmailBodyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates\\PasswordRecoveryInactiveEmailBody.htm");
            _serviceBusPublisher = new ServiceBusPublisher(serviceBusParams);
         }

        private Guid GenerateTemporaryKey()
        {
            return Guid.NewGuid();
        }

        public void ResetPasswordByUserIds(IEnumerable<Guid> userIds)
        {
            try
            {
                if (userIds == null)
                    throw new ArgumentNullException("The list of userIds is null");

                foreach (var userId in userIds)
                {
                    var tempKey = GenerateTemporaryKey().ToString();

                    SavePassword(userId, tempKey, DateTime.UtcNow.AddDays(7));

                    var user = _accountsRepository.GetUserByUserId(userId);

                    var emailBodyText = File.ReadAllText(_passwordChangeEmailBodyFile)
                                        .Replace("USER_FIRST_NAME", user.FirstName)
                                        .Replace("USER_LAST_NAME", user.LastName)
                                        .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login/change/{1}/{2}", _clientPlatformUrl, user.Email, tempKey))
                                        .Replace("EMAIL_BODY_PLACEHOLDER", _passwordResetEmailBody);

                    _emailHelper.SendEmail(new List<string> { user.Email }, null, _passwordChangeEmailSubject, emailBodyText).Wait();
                    Thread.Sleep(TimeSpan.FromSeconds(5));// let the email service do its devious job
                }
            }
            catch (ArgumentNullException ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ResetPasswordByUserIdError, DebugHelper.GetCallerInfo(), userIds.ToString(), ex);
                throw new CustomException((int)ErrorCodes.ErrorGettingUserById, "There was an error reseting the password. Please try again later.");
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ResetPasswordByUserIdError, DebugHelper.GetCallerInfo(), userIds.ToString(), ex);
                throw new CustomException((int)ErrorCodes.ErrorResetingPassword, "There was an error reseting the password. Please try again later.");
            }
        }

        public void ResetPasswordByUsername(string username, PasswordEmailMessageBody emailBody)
        {
            User user;
            try
            {
                user = _accountsRepository.GetUserByUsername(username);
            }
            catch (ArgumentNullException ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ResetPasswordError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.InvalidEmailAddress, "The email address was not found.");
            }

            ResetPasswordByUser(user, emailBody);
        }

        public void ResetPasswordByUser(User user, PasswordEmailMessageBody emailBody)
        {
            var username = user.Email;
            try
            {
                var tempKey = GenerateTemporaryKey().ToString();

                var emailBodyText = File.ReadAllText(_passwordChangeEmailBodyFile)
                                        .Replace("USER_FIRST_NAME", user.FirstName)
                                        .Replace("USER_LAST_NAME", user.LastName)
                                        .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login/change/{1}/{2}", _clientPlatformUrl, user.Email, tempKey));
                string subject = "";

                switch (emailBody)
                {
                    case PasswordEmailMessageBody.AccessGranted:
                        subject = _accessGrantedEmailSubject;
                        emailBodyText = (File.Exists(_accesGrantedEmailBodyFile)) ? File.ReadAllText(_accesGrantedEmailBodyFile).Replace("USER_FIRST_NAME", user.FirstName)
                                        .Replace("USER_LAST_NAME", user.LastName)
                                        .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login/change/{1}/{2}", _clientPlatformUrl, user.Email, tempKey)) :
                                        emailBodyText.Replace("EMAIL_BODY_PLACEHOLDER", _accessGrantedEmailBody)
                                        ;
                        break;

                    case PasswordEmailMessageBody.ForgotPassword:
                        subject = _passwordChangeEmailSubject;
                        emailBodyText = File.ReadAllText(_passwordChangeOnlyEmailTemplate).Replace("USER_FIRST_NAME", user.FirstName)
                                        .Replace("USER_LAST_NAME", user.LastName)
                                        .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login/change/{1}/{2}", _clientPlatformUrl, user.Email, tempKey));

                        break;

                    case PasswordEmailMessageBody.PasswordExpired:
                        subject = _passwordChangeEmailSubject;
                        emailBodyText = emailBodyText.Replace("EMAIL_BODY_PLACEHOLDER", _passwordExpiredEmailBody);
                        break;

                    case PasswordEmailMessageBody.PasswordReset:
                        subject = _passwordChangeEmailSubject;
                        emailBodyText = emailBodyText.Replace("EMAIL_BODY_PLACEHOLDER", _passwordResetEmailBody);
                        break;
                }

                SavePassword(username, tempKey, DateTime.UtcNow.AddDays(7));
                _emailHelper.SendEmail(new List<string> { username }, null, subject, emailBodyText).Wait();
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ResetPasswordError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.ErrorResetingPassword, "There was an error reseting the password. Please try again later.");
            }
        }

        public void NotifyUserNotActive(string username)
        {
            try
            {
                var user = _accountsRepository.GetUserByUsername(username);

                string subject = _passwordRecoveryInactiveAccountEmailSubject;
                var emailBodyText = File.ReadAllText(_passwordRecoveryInactiveAccountEmailBodyFile)
                    .Replace("USER_FIRST_NAME", user.FirstName)
                    .Replace("USER_LAST_NAME", user.LastName);

                _emailHelper.SendEmail(new List<string> { username }, null, subject, emailBodyText).Wait();
            }
            catch (ArgumentNullException ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.NotifyUserNotActiveError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.InvalidEmailAddress, "The email address was not found.");
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.NotifyUserNotActiveError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.ErrorNotifyingUserInactive, "There was an error. Please try again later.", ex);
            }
        }

        public void ChangePassword(string username, string oldPassword, string newPassword)
        {
            try
            {
                var user = _accountsRepository.GetUserByUsername(username);

                var emailBody = File.ReadAllText(_passwordSuccessfullyChangedEmailBodyFile)
                                    .Replace("USER_FIRST_NAME", user.FirstName)
                                    .Replace("USER_LAST_NAME", user.LastName)
                                    .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login", _clientPlatformUrl));

                if (string.IsNullOrEmpty(oldPassword)) return;

                var clientUser = _accountsRepository.GetClientUserByUserId(user.Id);

                if (!PasswordManager.VerifyHashedPassword(clientUser.Password, oldPassword))
                {
                    SavePassword(username, newPassword, DateTime.UtcNow.AddMonths(6));
                    _emailHelper.SendEmail(new List<string> { username }, null, _passwordSuccessfullyChangedEmailSubject, emailBody).Wait();
                }
                else
                {
                    //todo Soryn ?
                }
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ChangePasswordError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.ErrorResetingPassword, ex.Message);
            }
        }

        public void SetPassword(string username, string password)
        {
            try
            {
                if (string.IsNullOrEmpty(password))
                    throw new CustomException((int)ErrorCodes.ErrorResetingPassword, "Password is null or empty");

                var user = _accountsRepository.GetUserByUsername(username);

                var emailBody = File.ReadAllText(_passwordSuccessfullyChangedEmailBodyFile)
                    .Replace("USER_FIRST_NAME", user.FirstName)
                    .Replace("USER_LAST_NAME", user.LastName)
                    .Replace("CHANGE_PASSWORD_HYPERLINK", string.Format("{0}/#/login", _clientPlatformUrl));

                SavePassword(username, password, DateTime.UtcNow.AddMonths(6));
                _emailHelper.SendEmail(new List<string> {username}, null, _passwordSuccessfullyChangedEmailSubject,
                        emailBody)
                    .Wait();
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.ChangePasswordError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.ErrorResetingPassword, ex.Message);
            }
        }

        private void SavePassword(string username, string password, DateTime expirationDate)
        {
            var hashedPassword = PasswordManager.HashPassword(password);

            Tuple<string, string, DateTime> authCredentials = new Tuple<string, string, DateTime>(username, hashedPassword, expirationDate);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(authCredentials), ReplicationUtils.EntityType.AuthCredentials, ReplicationUtils.Operation.SavePasswordByUsername);
            _accountsRepository.SavePasswordByUsername(username, hashedPassword, expirationDate);
        }

        private void SavePassword(Guid userId, string password, DateTime expirationDate)
        {
            var hashedPassword = PasswordManager.HashPassword(password);

            Tuple<Guid, string, DateTime> authCredentials = new Tuple<Guid, string, DateTime>(userId, hashedPassword, expirationDate);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(authCredentials), ReplicationUtils.EntityType.AuthCredentials, ReplicationUtils.Operation.SavePasswordByUserId);
            _accountsRepository.SavePasswordByUserId(userId, hashedPassword, expirationDate);
        }

        public bool ValidateAccountStatus(string username)
        {
            try
            {
                var user = _accountsRepository.GetUserByUsername(username.Trim());

                return user.Status == Entities.Status.Active;
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.LoginAttemptError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.InvalidEmailAddress, ex.Message);
            }
        }

        public bool ValidatePasswordStatus(string username)
        {
            try
            {
                var user = _accountsRepository.GetClientUserByUsername(username.Trim());

                bool passwordIsActive = user.PasswordExpirationDate >= DateTime.UtcNow;

                if (!passwordIsActive)
                {
                    ResetPasswordByUsername(username, PasswordEmailMessageBody.PasswordExpired);
                }

                return passwordIsActive;
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.CallerError(AuthOperationsMessage.LoginAttemptError, DebugHelper.GetCallerInfo(), username, ex);
                throw new CustomException((int)ErrorCodes.ExpiredPassword, ex.Message);
            }
        }

        public static string DefaulHashedPasswordIpLogin()
        {
            return PasswordManager.HashPassword(DEFAULT_IP_LOGIN_PASSWORD);
        }

        public void AcceptLegalAgreement(string username)
        {
            var user = _accountsRepository.GetUserByUsername(username.Trim());
            user.IsLegalAgreementAccepted = true;
            _accountsRepository.SaveUser(user);
        }

        public void AcceptFirstTimeRRUser(string username)
        {
            var user = _accountsRepository.GetUserByUsername(username.Trim());
            if (user.FirstTimeUserAccepted != null)
            { 
                user.FirstTimeUserAccepted.IsRiskRatingAccepted = true;
            } else
            {
                user.FirstTimeUserAccepted = new FirstTimeUser { IsRiskRatingAccepted = true };
            }
            _accountsRepository.SaveUser(user);
        }

        public void AcceptFirstTimeSPUser(string username)
        {
            var user = _accountsRepository.GetUserByUsername(username.Trim());
            if(user.FirstTimeUserAccepted != null)
            { 
                user.FirstTimeUserAccepted.IsSustainableProductsAccepted = true;
            } else
            {
                user.FirstTimeUserAccepted = new FirstTimeUser { IsSustainableProductsAccepted = true };
            }

            _accountsRepository.SaveUser(user);
        }
    }
}
